<?php

namespace App\Services\Bolai;

use App\Constants\ErrorCode;
use App\Exception\BusinessException;
use App\Model\Device;
use App\Model\DevicePort;
use App\Model\DeviceSignalLog;
use App\Model\Normal;
use App\Model\Order;
use Carbon\Carbon;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Swoole\Server;

/**
 * BL服务
 */
class BlService
{
    const BKV_VALID_HEAD = ['fcfe', 'fcff'];// 有效的帧头
    const BKV_VALID_END = ['fcee', 'fcfe'];// 有效的帧尾


    public array $faultMap = [
        0 => 'over_voltage',      // Bit0
        1 => 'under_voltage',     // Bit1
        2 => 'relay_stick',       // Bit2
        3 => 'fuse_cut_out',      // Bit3
        4 => 'metering_error',    // Bit4
        5 => 'over_temp',         // Bit5
        6 => 'leakage_alarm',     // Bit6
        7 => 'charger_error',     // Bit7
        8 => 'aerosol_trigger',   // Bit8
        9 => 'ground_fault',      // Bit9
        10 => 'short_circuit',    // Bit10
        11 => 'cp_fault',         // Bit11
        12 => 'over_current',     // Bit12
    ];


    private $cmd;

    public $message;

    private $bkvData;

    #[Inject]
    protected Device $device;

    #[Inject]
    protected DevicePort $devicePort;

    #[Inject]
    protected Order $order;

    protected LoggerInterface $logger;

    public function __construct(LoggerFactory $loggerFactory)
    {
        $this->logger = $loggerFactory->get('log', 'device');
    }

    /**
     * 回复消息
     * @param int $fd
     * @param string $message
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function sendToDevice(int $fd, string $message)
    {
        // 获取 Swoole Server 对象
        $server = ApplicationContext::getContainer()->get(Server::class);
        // 检查连接是否存在
        if ($server->exists($fd)) {
            $server->send($fd, $message);
            Normal::logger($fd . 'fd---send-----success', 'notify-event');
        } else {
            Normal::logger($fd . 'fd---not found', 'notify-event');
        }
    }

    /**
     * 通过MAC下发设备消息
     * @param string $mac
     * @param string $message
     * @return void
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public static function sendToDeviceByMac(string $mac, string $message): void
    {
        // 获取 Swoole Server 对象
        $server = ApplicationContext::getContainer()->get(Server::class);
        // 检查连接是否存在
        if ($server->exists($mac)) {
            $fd = Device::where('mac', $mac)->value('fd');
            $server->send($fd, $message);
            Normal::logger('mac---send-----success', 'notify-event');
        } else {
            Normal::logger('mac---not found', 'notify-event');
        }
    }

    /**demo
     * @return void
     */
    public function demo(): void
    {
        $value = [2, 3];
        $b = new BKV();
        $b->addByNumberKey(1, $value); // 添加数字键
        $b->addByStringKey("version", $value); // 添加字符串键
        $b->addByStringKey("test", "hello"); // 添加字符串键和字符串值
        $result = $b->pack();
        echo "Packed result (hex): " . $b->bufferToHex($result) . "\n";
        $upr = BKV::unpack($result);
        if ($upr['code'] === 0) {
            echo "Unpack kv size: " . count($upr['bkv']->items()) . "\n";
            $upr['bkv']->dump(); // 打印解包后的键值对
        } else {
            echo "Unpack fail: " . $upr['code'] . "\n";
        }
    }

    public function setOff($fd)
    {
        $device = $this->device->where('fd', $fd)->first();
        if ($device) {
            $device->fd = 0;
            $device->inline = 0;
            $device->save();
        }
    }

    /**解析
     * @param $data
     * @return $this
     * @throws \Exception
     */
    public function parse($data = [])
    {
        try {
            $parsedData['frameHeader'] = bin2hex(substr($data, 0, 2));
            // 2. 帧长（2字节）
            $parsedData['frameLength'] = unpack('n', substr($data, 2, 2))[1];
            // 3. BKV 数据域
            $bkvData = substr($data, 4, $parsedData['frameLength'] - 3);
            $this->bkvData = bin2hex($bkvData);
            $b = new BKV();
            $upr = BKV::unPack($b->hexToBuffer($this->bkvData));
            if ($upr['code'] !== 0) {
                throw new BusinessException(ErrorCode::BKV_UNPACK_FAIL);
            }
            $this->message = $upr['bkv']->dump(); // 打印解包后的键值对
            return $this;
        } catch (\Exception $e) {
            Normal::logger('parse-error--------' . $e->getMessage() . '------' . $e->getTraceAsString(), 'notify-event');
        }
    }

    /**
     * 解析之后执行方法
     * @param $fd
     * @return void
     */
    public function handle($fd): void
    {
        $this->cmd = hexdec($this->message[0x01]['value']);
        $message = '';
        switch ($this->cmd) {
            case 0x1001:// 心跳上报
//                Normal::logger($fd . 'heart-get--' . $this->bkvData . '---', 'notify-event');
                $message = $this->recvHeart();
//                Normal::logger($fd . 'heart-callback--' . $this->cmd . '---', 'notify-event');
                $this->saveHeart($fd);
                break;
            case 0x1002:// 状态上报
//                Normal::logger($fd . 'status-get--' . $this->bkvData . '---', 'notify-event');
                $message = $this->recvStatus();
//                Normal::logger($fd . 'status-callback--' . $this->cmd . '----', 'notify-event');
                $this->saveStatus($fd);
                break;
            case 0x1003:// 查询状态(设备回复)
                $this->saveStatus($fd);
                break;
            case 0x1007:
//         Normal::logger('start-callback----0x1007----' . $this->bkvData . ' -----------', 'notify-event');
                $this->changeOrderStatus();
                break;
            case 0x1004:// 充电结束上报0x1004（设备上报）
 //               Normal::logger('end-callback----0x1004----' . $this->bkvData . ' -----------', 'notify-event');
                $message = $this->recvEnd();
                $this->endPortSave();
                break;
            case 0x100c:// 查询设备参数0x100c（设备回复）
//                Normal::logger('end-callback-select-params-0x100c-' . $this->bkvData . ' -----------', 'notify-event');
                $this->saveSelectParam();
                break;
            case 0x100b:// 设置设备参数0x100b（设备回复）
//                Normal::logger('end-callback-set-params-0x100b-' . $this->bkvData . ' -----------', 'notify-event');
                break;
            case 0x100d:// 系统重启0x100d（设备回复）
 //               Normal::logger('end-callback-system-restart-0x100d-' . $this->bkvData . ' -----------', 'notify-event');
                break;
            case 0x1013:
                $message = $this->recvThing();
                break;
            default:
                break;
        }
        if ($message) {
            $this->sendToDevice($fd, $message);
        }
    }

    /**
     *
     */
    public function changeOrderStatus()
    {

        try {
            $mac = $this->message[0x03]['value'];
            $device = $this->device->where('mac', $mac)->first();
            $port = hexdec($this->message[0x08]['value']);
            $order = $this->order
                ->where('device_id', $device->id)
                ->where('channel_number', $port)
                ->where('status', Order::STATUS_PAID)
                ->orderBy('id', 'desc')
                ->first();
            if (!$order) {
                throw new BusinessException(ErrorCode::ORDER_NOT_FOUND);
            }
            $callbackStatus = $this->message[0x97]['value'];
            switch ($callbackStatus) {
                case 0x00:
                    $order->status = Order::STATUS_USING;
                    $order->estimate_stop_at = Carbon::now();
                    $order->save();
                    break;
                default:
                    $order->fail('开锁异常');
                    break;

            }
        } catch (\Exception $e) {
            Normal::logger('order-status-save-error--------' . $e->getMessage() . $e->getTraceAsString(), 'notify-event');
        }
    }

    /**
     * 充电结束,保存数据
     * @return void
     */
    public function endPortSave(): void
    {
        try {
            $mac = $this->message[0x03]['value'];
            $device = $this->device->where('mac', $mac)->first();
            $port = hexdec($this->message[0x08]['value']);
            $order = $this->order
                ->where('device_id', $device->id)
                ->where('channel_number', $port)
                ->where('status', Order::STATUS_USING)
                ->orderBy('id', 'desc')
                ->first();
            if (!$order) {
                throw new BusinessException(ErrorCode::ORDER_NOT_FOUND);
            }
            if (isset($this->message[0x09]['value'])) {
                $portArr = $device->statusParse(hexdec($this->message[0x09]['value']));
            } else {
                $portArr['charge_status'] = 0;
            }
            $portArr['times'] = hexdec($this->message[0x0a]['value']);
            $post['power'] = $portArr['power'] = hexdec($this->message[0x0b]['value']);
            $portArr['current'] = hexdec($this->message[0x0c]['value']);
            $post['use_elec'] = $portArr['elec'] = hexdec($this->message[0x0d]['value']);
            $post['minutes'] = $portArr['min'] = hexdec($this->message[0x0e]['value']);
            $this->devicePort->where('device_id', $device->id)
                ->where('port_bin', $port)
                ->update($portArr);
            if (isset(Order::STOP_MAP[hexdec(($this->message[0x2f]['value']))])) {
                $post['stop_reason'] = Order::STOP_MAP[hexdec(($this->message[0x2f]['value']))];
            }else{
                $post['stop_reason'] = '未知异常';
            }
            $post['use_elec'] =  $portArr['elec'];
            switch (hexdec($this->message[0x12]['value'])) {
                case 1:// 按时
                    break;
                case 2:// 按量
                    break;
                case 3:// 按金额的功率
                    $post['use_fee'] = hexdec($this->message[0x30]['value']) ? hexdec($this->message[0x30]['value']) / 100 : 0;
                    break;
                case 4:// 电量+服务费
                    $post['use_fee'] = hexdec($this->message[0x85]['value']) ? hexdec($this->message[0x85]['value']) / 100 : 0;
                    $post['charge_service'] = hexdec($this->message[0x86]['value']) ? hexdec($this->message[0x86]['value']) / 100 : 0;
                    break;
                case 5:// 按时间的分功率
                    break;
                case 6:// 电量+服务费_新
                    break;
                default:
                    break;
            }
            try {
                $service = new PackService();
                if ($post['minutes']*60 < 180) {
                    $post['id'] = $order->id;
                    $post['type'] = 'refund';
//                    $order->fail($order->stop_reason);
                } else {
                    $post['id'] = $order->id;
                    $post['type'] = 'stop';
//                    $order->stop($order->stop_reason);
                }
                $service->post_curl('https://www.chdyzw.com/user/order/do_refund',$post);
            } catch (\Exception $e) {
                Normal::logger('结算代码-save-error--------' . $e->getMessage() . $e->getTraceAsString(), 'notify-event');
            }
        } catch (\Exception $e) {
            Normal::logger('end-port-save-error--------' . $e->getMessage() . $e->getTraceAsString(), 'notify-event');
        }
    }

    /**回复0x1013
     * @return void
     */
    public function recvThing()
    {
        $b = new BKV();
        $b->addByNumberKey(0x01, 0x1001);
        $b->addByNumberKey(0x02, $this->generateRequestId());
        $b->addByNumberKey(0x03, (int)$this->message[0x03]['value']);

    }


    public function recvEnd()
    {
        $b = new BKV();
        $b->addByNumberKey(0x01, 0x1004);
        $b->addByNumberKey(0x02, $this->generateRequestId());
        $b->addByNumberKey(0x03, (int)$this->message[0x03]['value']);
        $b->addByNumberKey(0x08, (int)$this->message[0x08]['value']);
        $b->addByNumberKey(0x0f, 1);
        $result = $b->pack();
        Normal::logger('结束-callback--------' . $b->bufferToHex($result), 'notify-event');
        return self::buildFrame($b->bufferToHex($result));
    }

    /**回复心跳信息
     * @return string
     */
    public function recvHeart()
    {
        try {
            $b = new BKV();
            $b->addByNumberKey(0x01, 0x1001);
            $b->addByNumberKey(0x02, $this->generateRequestId());
            $b->addByNumberKey(0x03, (int)$this->message[0x03]['value']);
            $b->addByNumberKey(0x06, $b->getBcdTime());
            $result = $b->pack();
            Normal::logger('heart-callback--------' . $b->bufferToHex($result), 'notify-event');
            return self::buildFrame($b->bufferToHex($result));
        } catch (\Exception $e) {
            Normal::logger('recvHeart-error--------' . $e->getMessage(), 'notify-event');
        }
    }

    /**保存心跳信息
     * @param $fd
     * @return void
     */
    public function saveHeart($fd): void
    {
        try {
            $mac = $this->message[0x03]['value'];
            $device = Device::query()->where('mac', $mac)->first();
            $device->inline = 1;
            $device->last_online_at = Carbon::now();
            $device->signal = hexdec($this->message[0x05]['value']);
            $device->card_no = $this->message[0x48]['value'];
            $device->fd = $fd;
            $device->save();
            // 无端口信息,请求查询端口状态
            $device_port_into = DevicePort::where('device_id',$device->id)->first();
            if (!$device_port_into) {
                (new PackService)->selectDeviceStatus($device);
            }
            $this->saveHeartHour($device->id, $device->signal);
        } catch (\Exception $e) {
            Normal::logger('saveHeart-error--------' . $e->getMessage() . $e->getTraceAsString(), 'notify-event');
        }
    }

    /**
     * 每两个小时记录一次心跳日志
     * @param $device_id
     * @param $signal
     * @return void
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function saveHeartHour($device_id, $signal): void
    {
        try {
            $container = ApplicationContext::getContainer();
            $redis = $container->get(Redis::class);
            $key = 'heart:hour:' . $device_id;
            $ttl = 7200;
            if ($redis->setnx($key, (string)$signal) || $redis->ttl($key) == -1) {
                // 键设置成功或者键已存在但没有设置过期时间
                $redis->expire($key, $ttl);
                $model = new DeviceSignalLog;
                $model->device_id = $device_id;
                $model->signal = $signal;
                $model->save();
            }
        } catch (\Exception $e) {
            Normal::logger('saveHeartHour-error--------' . $e->getMessage() . $e->getTraceAsString(), 'notify-event');
        }
    }

    /**
     * 保存查询设别参数信息
     * @return void
     */
    public function saveSelectParam(): void
    {
        try {
            $mac = $this->message[0x03]['value'];
            $model = $this->device->where('mac', $mac)->first();
            $model->report_cycle = hexdec($this->message[0x1E]['value']);
            $model->heart_cycle = hexdec($this->message[0x20]['value']);
            $model->full_charge_delay = hexdec($this->message[0x21]['value']);
            $model->null_charge_delay = hexdec($this->message[0x22]['value']);
            $model->full_charge_power = hexdec($this->message[0x23]['value']);
            $model->null_charge_power = hexdec($this->message[0x24]['value']);
            $model->temp_threshold = hexdec($this->message[0x25]['value']);
            $model->threshold_i = hexdec($this->message[0x10]['value']);
            $model->threshold_p = hexdec($this->message[0x11]['value']);
            $model->max_charge_time = hexdec($this->message[0x59]['value']);
            $model->trickle_throld = hexdec($this->message[0x60]['value']);
            if (isset($this->message[0x68]['value'])) {
                $model->charge_single_amount = hexdec($this->message[0x68]['value']);
            }
            $model->save();
        } catch (\Exception $e) {
            Normal::logger('saveSelectParam-error--------' . $e->getMessage(), 'notify-event');
        }
    }

    /**
     * @return void
     */
    public function saveStatus($fd)
    {
        try {
            $mac = $this->message[0x03]['value'];
            $device = $this->device->where('mac', $mac)->first();
            $number = 0;

            foreach ($this->message as $k => $v) {
                if ($k >= 0x1c) {
                    $number++;
                    $hexData = $v['value'];
                    $b = new BKV();
                    $upr = BKV::unPack($b->hexToBuffer($hexData));
                    if ($upr['code'] !== 0) {
                        Normal::logger('saveStatus-error--------' . $mac, 'notify-event');
                    }
                    $portInfo = $upr['bkv']->dump(); // 打印解包后的键值对
                    $device->portUpdate($portInfo);
                }
            }
//            $device->channel = $number;
            $device->fd = $fd;
            $device->save();
        } catch (\Exception $e) {
            Normal::logger('saveStatus-error--------' . $e->getMessage(), 'notify-event');
        }
    }


    public function recvStatus()
    {
        try {
            $b = new BKV();
            $b->addByNumberKey(0x01, 0x1002);
            $b->addByNumberKey(0x02, $this->generateRequestId());
            $b->addByNumberKey(0x03, (int)$this->message[0x03]['value']);
            $b->addByNumberKey(0x0f, 1);
            $result = $b->pack();
            Normal::logger('status-callback--------' . $b->bufferToHex($result), 'notify-event');
            return self::buildFrame($b->bufferToHex($result));
        } catch (\Exception $e) {
            Normal::logger('recvStatus--------' . $e->getMessage(), 'notify-event');
        }
    }


    public function generateRequestId(): string
    {
        return random_bytes(8);
    }

    public static function buildFrame(string $bkvData): string
    {
        // 1. 计算帧长（帧长字段 + 数据域 + 校验和字段）
        $bkvData = hex2bin($bkvData);
        $frameLength = strlen($bkvData) + 3; // 2 字节帧长 + 1 字节校验和
        $frameLengthHex = pack('n', $frameLength); // 将帧长转换为 2 字节二进制

        // 2. 计算校验和（累加校验和）
        $checksum = self::calculateChecksum($frameLengthHex . $bkvData);
        $checksumHex = pack('C', $checksum); // 将校验和转换为 1 字节二进制

        // 3. 拼接完整帧
        $frame = hex2bin('fcff') // 帧头（平台下行）
            . $frameLengthHex // 帧长
            . $bkvData // 数据域（BKV 数据）
            . $checksumHex // 校验和
            . hex2bin('fcee'); //
        return $frame;
    }

    private static function calculateChecksum(string $data): int
    {
        $checksum = 0;
        for ($i = 0; $i < strlen($data); $i++) {
            $checksum += ord($data[$i]);
        }
        return $checksum & 0xFF; // 取低 8 位
    }

}
